﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics.OpenGL;


namespace XGL
{

    /// <summary>
    /// The all-important XEvent delegate. Designed for both regular methods and/or embeded lambada expressions.
    /// There are various lambada tutorials on the net if you are new to C#.
    /// </summary>
    /// <param name="time">The current time when the event was called.</param>
    /// <param name="caller">The object that most likely initiated the event.</param>
    /// <param name="msg">Optional message string.</param>
    /// <param name="data">Optional data object.</param>
    /// <returns>may or may not return an object, depending on situation/event.</returns>
    public delegate object GEvent(float time,object callerl,string msg,object data);



    #region XApp Class.
    /// <summary>
    /// The primary application class, used to house and maintain your actively running game or application/tool.
    /// You should extend/inherit this class, and override it's exposed interface to obtain the functionality you desire.
    /// Be sure to check out XState, the api's in-house state management system.
    /// </summary>
    /// 
    public class GApp : GameWindow
    {
        

        /// <summary>
        /// The dictionary of events linked to the currently running app. Can be methods or lambada expressions.
        /// </summary>
        public Dictionary<string, GEvent> EventMap = new Dictionary<string, GEvent>();


        /// <summary>
        /// You should set this via base() when extending this class. It lets you specify resolution/fullscreen and app title. 
        /// </summary>
        /// <param name="app">The title of the application.</param>
        /// <param name="width">The width of the application screen/window.</param>
        /// <param name="height">The height of the application screen/window</param>
        /// <param name="fullScreen">Weather to start full screen or not. true = fullscreen.</param>
        public GApp(string app, int width, int height, bool fullScreen) : base()
        {

        }
        /// <summary>
        /// Lets you specify an event lambada expression to quickly prototype or rapidly design your app.
        /// Traditional override methods are still present, for a more structured approach.
        /// </summary>
        /// <param name="id">Name of the event. I.e Update/Draw/Init/LoadContent etc.</param>
        /// <param name="evt">The delegate or lamabada expression to invoke when the event is fired/activated.</param>
        public void SetEvent(string id, GEvent evt)
        {
            EventMap.Add(id, evt);
        }
        /// <summary>
        /// Iniate an event with or without parameters.
        /// </summary>
        /// <param name="id">The identification of the event.</param>
        /// <param name="caller">the calling object, if any is required.</param>
        /// <param name="msg">optional message</param>
        /// <param name="data">optional data object</param>
        public void CallEvent(string id, object caller=null, string msg=null, object data=null)
        {
            float time = 0.0f;
            if (!EventMap.ContainsKey(id)) return;
            EventMap[id](time, (object)caller, (string)msg, (object)data);
        }

        protected virtual void Initialize()
        {
            // TODO: Add your initialization logic here
            RegisterEvents();
            OnInit();
            CallEvent("Init",this);
     
        }
        public virtual void RegisterEvents()
        {

        }
        public virtual void OnInit()
        {
           
        }
        public virtual void OnDraw()
        {

        }
        public virtual void OnLoadContent()
        {

        }
        protected virtual void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
          
            OnLoadContent();
            CallEvent("LoadContent",this);
           /*
            foreach (var s in States.Keys)
            {
                var st = States[s];
                st.OnLoadContent();

            }
            */
            // TODO: use this.Content to load your game content here
        }
        protected virtual void UnloadContent()
        {
            CallEvent("UnloadContent",this);
            // TODO: Unload any non ContentManager content here
        }
        public virtual void OnUpdate()
        {

        }
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected virtual void Update()
        {
            // Allows the game to exit
           
            OnUpdate();
            // TODO: Add your update logic here
            CallEvent("Update", this);
  
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected virtual void Draw()
        {
          
            // TODO: Add your drawing code here
            OnDraw();
            CallEvent("Draw", this);

          
        }
    }

    #endregion

    public enum GRenderMode
    {
        Triangles,Quads,TriStrips,QuadStrips
    }

    public class GState
    {
        public GRenderMode RenderMode;
        public Vector4 RenderColor;
        public bool AutoCompile = true;
    }



    public class GRenderList
    {
        public bool IsCompiled { get; set; }
        internal int VB_Pos, VB_Norm, VB_UV1, VB_UV2, VB_UV3, VB_Col, VB_BI, VB_Tan;
        public GRenderMode ListMode { get; set; }
        public List<GVertex> List = new List<GVertex>();
        public GRenderList(List<GVertex> list,GRenderMode mode){
            List = list;
            ListMode = mode;
            if (G.State.AutoCompile)
            {
                CompileList();
            }
        }

        public void CompileList()
        {
            IsCompiled = true;
            GL.GenBuffers(1, out VB_Pos);
            Vector3[] pos = new Vector3[List.Count];
            Vector3[] norm = new Vector3[List.Count];
            Vector3[] uv1 = new Vector3[List.Count];
            Vector4[] col = new Vector4[List.Count];
            Vector3[] bi = new Vector3[List.Count];
            Vector3[] tan = new Vector3[List.Count];

            int i=0;
            foreach (var v in List)
            {
                pos[i] = v.Pos;
                norm[i] = v.Norm;
                uv1[i] = v.MainUV;
                col[i] = v.Col;
                bi[i] = v.Bi;
                tan[i] = v.Tan;
                i++;
            }

            int buf = VB_Pos;


            SetBuffer(pos, VB_Pos);
            SetBuffer(norm, VB_Norm);
            SetBuffer(uv1, VB_UV1);
            SetBuffer4(col, VB_Col);


        }

        private static void SetBuffer(Vector3[] pos, int buf)
        {
            GL.BindBuffer(BufferTarget.ArrayBuffer, buf);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, new IntPtr(pos.Length * Vector3.SizeInBytes), pos, BufferUsageHint.StaticDraw);

        }
        private static void SetBuffer4(Vector4[] pos, int buf)
        {
            GL.BindBuffer(BufferTarget.ArrayBuffer, buf);
            GL.BufferData<Vector4>(BufferTarget.ArrayBuffer, new IntPtr(pos.Length * Vector4.SizeInBytes), pos, BufferUsageHint.StaticDraw);

        }
        public GVertex EndVertex
        {
            get
            {
                return List[List.Count - 1];
            }
        }
        public void Render()
        {
            switch (ListMode)
            {
                case GRenderMode.Triangles:
                    GL.Begin(BeginMode.Triangles);
                    break;
                case GRenderMode.Quads:
                    GL.Begin(BeginMode.Quads);
                    break;
                case GRenderMode.TriStrips:
                    GL.Begin(BeginMode.TriangleStrip);
                    break;
            }
            foreach (var v in List)
            {
                GL.TexCoord3(v.MainUV);
                GL.Color4(v.Col);
                GL.Normal3(v.Norm);
                GL.Vertex3(v.Pos);
            }
            GL.End();
        }
    }

    /// <summary>
    /// contains information regarding the world of G. 
    /// i.e, current render lists, and so on.
    /// </summary>
    public static class GWorld
    {
        public static GRenderList CurList = new GRenderList(new List<GVertex>(),GRenderMode.Triangles); 
       
        public static GVertex Cur
        {
            get
            {
                return CurList.EndVertex;
            }
        }
    }

    /// <summary>
    /// Used to perform a lambada expression on a specified/given vertex.
    /// Used to speed up developing, rather than add something new.
    /// </summary>
    /// <param name="v">The vertex to be operated on.</param>
    public delegate void GVertexOp(GVertex v);


    /// <summary>
    /// Transient(temporary) class to contain one information
    /// for one Vertex. A Vertex is one point of a mesh, sprite,
    /// and contains color/normal/and equilivent information.
    /// It is generally constructed automatically by the G api.
    /// </summary>
    public class GVertex
    {
        /// <summary>
        /// Position in undefined space(Usually world, but sometimes local) 
        /// of the vertex.
        /// </summary>
        public Vector3 Pos = Vector3.Zero;
        /// <summary>
        /// The normal (direction of surface) of the vertex.
        /// </summary>
        public Vector3 Norm = Vector3.Zero;
        /// <summary>
        /// The main texture mapping coord. Used to specify
        /// at which point the vertex should fetch texture data from.
        /// It is usually spread out across the triangle/quad/etc and filtered,
        /// to produce a final clean look at any viewing angle/distance./
        /// Mip-mapping can help improve quality further.
        /// </summary>
        public Vector3 MainUV = Vector3.Zero;
        /// <summary>
        /// Secondary mapping coord, for many purposes, such as light mapping.
        /// </summary>
        public Vector3 SecondUV = Vector3.Zero;
        /// <summary>
        /// Auxillary mapping coord, again for a varied number of purposes.
        /// </summary>
        public Vector3 AuxUV = Vector3.Zero;
        /// <summary>
        /// The color of the vertex, this may or may not be used by the rendering code.
        /// If shaders are active for example, they may use their own color paramters and circumvent this.
        /// </summary>
        public Vector4 Col = new Vector4(1, 1, 1, 1);

        /// <summary>
        /// Tangent of the vertex, useful for normal mapping and other purposes.
        /// </summary>
        public Vector3 Tan = Vector3.Zero;

        /// <summary>
        /// The BiNormal, again used in normal mapping etc.
        /// </summary>
        public Vector3 Bi = Vector3.Zero;
    }

    public delegate void GRenderOp();

    public delegate void GStateOp(GState state);

    /// <summary>
    /// The primary 'G' interface *static* class. Do not instantiate this class, simply use it as any other static class.
    /// </summary>
    public static class G
    {
        internal static List<GState> StateStack { get; set; }
        internal static GState State
        {
            get
            {
                if (StateStack.Count == 0) return null;
                return StateStack[StateStack.Count - 1];
            }

        }
        public static void StateOp(GStateOp op)
        {
        }
        static G()
        {

            StateStack = new List<GState>(); 
            StateStack.Add(new GState());

        }

        public static void PushState()
        {
            GState xs = new GState();
            xs.RenderColor = State.RenderColor;
            xs.RenderMode = State.RenderMode;
        }

        public static void PopState()
        {
            StateStack.Remove(State);
        }


        /// <summary>
        /// Begins a batch rendering of multiple/singular rendering elements,
        /// as specified by the XRenderMode mode parameter.
        /// Rendering is not performed until Finish is called.
        /// You can use the list methods to pre-generate highly optimized gpu
        /// versions of your data, and actively update live data, yet render
        /// as fast as your gpu/vertex layout permits. See Example programs for ideas.
        /// </summary>
        /// <param name="mode">Which type of rendering element to render.</param>
        public static void Begin(GRenderMode mode)
        {
            GWorld.CurList.List.Add(new GVertex());            
        }

        public static void Color(Vector4 c)
        {
            GWorld.Cur.Col = c;
        }
        public static void Color(float r, float g, float b, float a)
        {

            GWorld.Cur.Col = new Vector4(r, g, b, a);
        }

        public static void Vertex(GVertexOp op)
        {
            op(GWorld.Cur);
            NextVertex();
        }
        public static void Vertices(params GVertexOp[] opList)
        {
            foreach (var op in opList)
            {
                op(GWorld.Cur);
                NextVertex();
            }
        }

        public static void Vertex(Vector3 pos)
        {
            GWorld.Cur.Pos = pos;
            NextVertex();
        }

        public static void Vertex(float x=0, float y=0, float z=0)
        {
            GWorld.Cur.Pos = new Vector3(x, y, z);
            NextVertex();
        }

        public static void TexCoord(Vector3 uv)
        {
            GWorld.Cur.MainUV = uv;
        }
        public static void TexCoord(float x=0, float y=0,float z=0)
        {
            GWorld.Cur.MainUV = new Vector3(x, y, z);
        }


        

        public static void NextVertex()
        {
            GWorld.CurList.List.Add(new GVertex());
        }

        /// <summary>
        /// Helper to allow you to embed a labada expression within the appropriate begin/finish calls.
        /// </summary>
        /// <param name="mode">The rendering mode to automatically iniate.</param>
        /// <param name="op">The lambada expression that performs your rendering.</param>
        public static void RenderOp(GRenderMode mode,GRenderOp op)
        {
            Begin(mode);
            op();
            Finish();
        }

        public static void Finish()
        {
        
        }


    }
}
